#include "RenderQueue.h"
#include "Renderer.h"

namespace Nezha
{
	// opaque sorter
	class RenderObjectHashSorter
	{
	public:
		bool operator () (const RenderQueueEntry& lhs, const RenderQueueEntry& rhs) const
		{
			// app specified priority.
			if(lhs.HashValue.LayerVal < rhs.HashValue.LayerVal)
			{
				return true;
			}
			else if(lhs.HashValue.LayerVal > rhs.HashValue.LayerVal)
			{
				return false;
			}

			if(lhs.HashValue.PassVal < rhs.HashValue.PassVal)
			{
				return true;
			}
			else if(lhs.HashValue.PassVal > rhs.HashValue.PassVal)
			{
				return false;
			}

			if(lhs.HashValue.ShaderVal < rhs.HashValue.ShaderVal)
			{
				return true;
			}
			else if(lhs.HashValue.ShaderVal > rhs.HashValue.ShaderVal)
			{
				return false;
			}

			if(lhs.HashValue.MaterialVal < rhs.HashValue.MaterialVal)
			{
				return true;
			}
			else if(lhs.HashValue.MaterialVal > rhs.HashValue.MaterialVal)
			{
				return false;
			}

			if(lhs.HashValue.GeometryVal < rhs.HashValue.GeometryVal)
			{
				return true;
			}
			else if(lhs.HashValue.GeometryVal > rhs.HashValue.GeometryVal)
			{
				return false;
			}

			if(lhs.HashValue.StatesVal < rhs.HashValue.StatesVal)
			{
				return true;
			}
			else if(lhs.HashValue.StatesVal > rhs.HashValue.StatesVal)
			{
				return false;
			}

			return false;
		}
	};

	RenderQueue::RenderQueue()
		:mSortType(ST_MATERIAL)
	{
		mNumEntries = 128;
		mQueueEntries.reserve(mNumEntries);
	}

	RenderQueue::RenderQueue(SortType st)
		:mSortType(st)
	{
		mNumEntries = 128;
		mQueueEntries.reserve(mNumEntries);
	}

	RenderQueue::~RenderQueue()
	{

	}

	void RenderQueue::addRenderObject(RenderObject* ro)
	{
		if(!ro || ro->getNumMaterialLayers() == 0)
		{
			return;
		}

		RenderQueueEntry rqe;
		rqe.RObject = ro;

		// FIXME transparent objects!!
		u32 numMatLayers = ro->getNumMaterialLayers() <= MAX_NUM_MATERIAL_LAYERS ? ro->getNumMaterialLayers() : MAX_NUM_MATERIAL_LAYERS;
		for(u32 i = 0; i < numMatLayers; i++)
		{
			MaterialPtr mat = ro->getMaterial(i);
			u32 numPass = mat->getNumPass() <= MAX_NUM_PASSES ? mat->getNumPass() : MAX_NUM_PASSES;
			for(u32 j = 0; j < numPass; j++)
			{
				rqe.HashValue.LayerVal = (u16)ro->getRenderLayer();

				rqe.HashValue.PassVal = j;
				rqe.HashValue.GeometryVal = (u32)ro->getGeometryHash();

				RenderPass* rp = mat->_getPass(j);
				rqe.HashValue.ShaderVal = (u32)rp->getProgram();

				rqe.HashValue.MaterialVal = rp->getNumSamplers();
				rqe.HashValue.StatesVal = getStatesHash(rp);

				rqe.RPass = rp;
				rqe.RObject = ro;

				if(mNumEntries < mQueueEntries.size())
				{
					mQueueEntries[mNumEntries] = rqe;
				}
				else
				{
					mQueueEntries.push_back(rqe);
				}

				++mNumEntries;
			}
		}
	}

	u32 RenderQueue::getStatesHash(RenderPass* pass)
	{
		u32 sthash = 0;

#if NEZHA_SORT_WITH_STATES == 1

		// for now, we simply count states changes, since we suppose each render state API call 
		// costs the same time.(in fact it's not)
		Renderer* r = Renderer::GetGlobal();

		const RS_Blending& alpha = r->getDefaultBlending();
		const RS_CullOrder& cull = r->getDefaultCullOrder();
		const RS_Depth& depth = r->getDefaultDepth();
		const RS_PolygonOffset& offset = r->getDefaultPolygonOffset();
		const RS_Stencil& stencil = r->getDefaultStencil();
		const RS_FillMode& fm = r->getDefaultFillMode();

		sthash += r->differBlending(pass->mBlending, alpha);
		sthash += r->differCullOrder(pass->mCull, cull);
		sthash += r->differDepth(pass->mDepth, depth);
		sthash += r->differPolygonOffset(offset, pass->mOffset);
		sthash += r->differStencil(stencil, pass->mStencil);
		sthash += r->differFillMode(fm, pass->mFillMode);

#endif

		return sthash;
	}

	u32 RenderQueue::getMaterialHash(RenderPass* pass)
	{
		return pass->getNumSamplers();
	}

	void RenderQueue::clear()
	{
		mNumEntries = 0;
	}

	void RenderQueue::sort()
	{
		switch(mSortType)
		{
		case ST_MATERIAL:
			if(mNumEntries < mQueueEntries.size())
			{
				QueueEntryList::iterator endIter = mQueueEntries.begin() + mNumEntries;
				std::sort(mQueueEntries.begin(), endIter, RenderObjectHashSorter());
			}
			else
			{
				std::sort(mQueueEntries.begin(), mQueueEntries.end(), RenderObjectHashSorter());
			}
			break;
		case ST_FAR2NEAR:
			{
				// TODO
			}
			break;
		case ST_NEAR2FAR:
			{
				// TODO
			}
			break;
		}
	}

	void RenderQueue::draw()
	{
		Renderer* r = Renderer::GetGlobal();

		for(u32 i = 0; i < mNumEntries; i++)
		{
			RenderQueueEntry* rqe = &mQueueEntries[i];
			r->_draw(rqe);
		}
	}

}//end namespace Nezha